Trong bài viết trước chúng ta đã làm quen với Laravel Migration và Laravel Seeding là các công việc cần thiết khi làm việc với cơ sở dữ liệu giúp cho việc thiết kế kiến trúc và quản lý phiên bản cơ sở dữ liệu trở lên đơn giản. Như vậy, chúng ta đã có một cơ sở dữ liệu với đầy đủ các bảng các cột dữ liệu và có cả những dữ liệu test trong đó, trong phần tiếp theo này bạn sẽ được giới thiệu cách thức truy vấn và xử lý dữ liệu đang có trong cơ sở dữ liệu. Trước khi bắt đầu vào phần chính bài viết chúng ta sẽ điểm qua một số thuật ngữ sẽ sử dụng trong bài như “ORM là gì?“, “mô hình MVC là gì?“
1. ORM là gì?
ORM (Object Relational Mapping) là một kỹ thuật lập trình dùng để chuyển đổi dữ liệu giữa một hệ thống không hướng đối tượng như cơ sở dữ liệu sang hệ thống hướng đối tượng như lập trình hướng đôi tượng trong PHP. Kỹ thuật này tạo ra các đối tượng CSDL ảo có thể được lập trình trong mã nguồn và có nhiều ưu điểm như mã nguồn trở lên rõ ràng và dễ bảo trì, dễ dàng thao tác với dữ liệu và thực hiện việc tối ưu hệ thống thông qua việc sử dụng bộ đệm… Các công việc khó hoặc không thể xử lý ở database layer sẽ được đưa lên lớp ứng dụng.
![](https://danatec.vn/wp-content/uploads/2023/03/5.png)
2. Mô hình MVC là gì?
Khái niệm này đã được nhắc đến trong Laravel Routing, định tuyến người dùng, ở đây chúng ta sẽ chỉ sơ lược lại đôi chút. MVC (Model – View – Controller) là mô hình phân chia ứng dụng thành 3 thành phần, mỗi thành phần có nhiệm vụ riêng:
- Model chứa các logic nghiệp vụ và các thao tác với cơ sở dữ liệu.
- View thực hiện các công việc hiển thị và tương tác với người dùng.
- Controller làm nhiệm vụ điều hướng giữa các đối tượng tham gia hệ thống như điều hướng một yêu cầu từ người dùng (HTTP request chẳng hạn) đến cho một Model tương ứng xử lý sau đó kết quả trả về sẽ chuyển đến cho View để thực hiện các hiển thị và tương tác với người dùng cuối.
![](https://danatec.vn/wp-content/uploads/2023/03/6.png)
3. Các thuật ngữ liên quan đến cơ sở dữ liệu
Trong bài viết sẽ cố gắng dùng các thuật ngữ bằng tiếng Việt, tuy nhiên có một số thuật ngữ nếu viết bằng tiếng Việt khá dài hoặc không có một thuật ngữ tương tự trong tiếng Việt nên có lúc sử dụng cả thuật ngữ tiếng Anh. Danh sách các thuật ngữ cơ bản liên quan đến cơ sở dữ liệu: – Relational Database Management System (RDBMS): hệ quản trị cơ sở dữ liệu quan hệ là các ứng dụng được phát triển bởi các nhà cung cấp, giúp tạo ra các cơ sở dữ liệu quan hệ và quản trị chúng. Ví dụ: MySQL, SQL Server là các hệ quản trị cơ sở dữ liệu quan hệ.
- database: cơ sở dữ liệu là kho chứa dữ liệu, trong một hệ thống ứng dụng có thể sử dụng nhiều các database khác nhau.
- table: bảng dữ liệu record: các bản ghi dữ liệu
- relationship: mối quan hệ giữa các bảng dữ liệu.
- index: tạo chỉ mục cho dữ liệu giúp truy vấn dữ liệu được nhanh chóng.
- CRUD: viết tắt của Create, Read, Update, Delete là 4 thao tác cơ bản với một bản ghi.
4. Laravel Eloquent ORM
Với các thuật ngữ ở trên, chúng ta phần nào mường tượng được Laravel Eloquent ORM là gì?, vâng nó nói lên rằng Laravel đã sử dụng kỹ thuật ORM giúp lập trình viên thao tác dễ dàng hơn với cơ sở dữ liệu. Trong phần này chúng ta sẽ nói nhiều đến Laravel Eloquent Model (gọi tắt là Model) là một phần trong mô hình MVC ở trên, các Model này sẽ thao tác trực tiếp với cơ sở dữ liệu, xử lý các logic nghiệp vụ và trả dữ liệu về cho các Controller. Chúng ta sẽ lần lượt tìm hiểu cách thức tạo ra các Model và sử dụng chúng.
4.1 Tạo Model
Trong Laravel thông qua ORM mỗi bảng trong cơ sở dữ liệu sẽ là một Model, các hành động tương tác với CSDL sẽ được làm trên Model như truy vấn dữ liệu hay insert các bản ghi vào bảng… Các Model có thể được tạo ra thủ công bằng cách tạo file có phần mở rộng php hoặc tự động thông qua các câu lệnh artisan:
php artisan make:model Product
Khi đó nó sẽ tạo ra một lớp Product trong file Product.php nằm trong thư mục app.
4.2 Tạo Model cùng với file migrate
Một vấn đề đặt ra là khi bạn tạo Model nhưng bảng trong cơ sở dữ liệu chưa tồn tại thì sao? Ok, chúng ta sẽ phải thực hiện tạo một bảng mới thông qua Laravel Migration, Laravel cũng hỗ trợ bạn một cách tự động luôn là tạo Model kèm với file migrate luôn thông qua việc thêm tham số –migrate hoặc -m trong câu lệnh tạo Model:
php artisan make:model Product --migration
php artisan make:model Product -m
4.3 Một số quy ước ngầm định của Eloquent Model
4.3.1 Ngầm định tên bảng
Mỗi Model sẽ được mapping với một bảng dưới cơ sở dữ liệu, Laravel ngầm định một Model sẽ được map với một bảng có tên chính là tên Model với dạng số nhiều trong tiếng Anh (thêm s hoặc ies) còn được gọi là tiêu chuẩn đặt tên Snake case. (Xem them Các tiêu chuẩn đặt tên trong lập trình). Snake case ở đây sẽ biến một tên lớp dạng PascalCase sang tên bảng dạng underscore (gạch chân) và thêm số nhiều vào. Ví dụ Model Product ở trên sẽ tương ứng với bảng products. Nếu bạn muốn bỏ ngầm định này để map Model sang một bảng khác như my_products cũng rất đơn giản là sử dụng thuộc tính $table để khai báo.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
// Thay đổi các thiết lập ngầm định của Eloquent Model
protected $table = 'my_products';
public $primaryKey = 'product_code';
public $incrementing = false;
}
4.3.2 Ngầm định khóa chính
Eloquent Model ngầm định sử dụng trường có tên id là khóa chính (primary key), nếu bảng trong cơ sở dữ liệu sử dụng một trường khác là khóa chính chúng ta có thể khai báo thông qua thuộc tính 𝑝𝑟𝑖𝑚𝑎𝑟𝑦𝐾𝑒𝑦,𝑣ớ𝑖𝑡𝑟ườ𝑛𝑔𝑘ℎó𝑎𝑐ℎí𝑛ℎ𝑛à𝑦𝑛ế𝑢𝑘ℎô𝑛𝑔𝑝ℎả𝑖𝑙à𝑑ạ𝑛𝑔𝑑ữ𝑙𝑖ệ𝑢𝑠ố𝑛𝑔𝑢𝑦ê𝑛𝐼𝑛𝑡𝑒𝑔𝑒𝑟𝑣à𝑡ă𝑛𝑔𝑡ựđộ𝑛𝑔,𝑐ℎú𝑛𝑔𝑡𝑎𝑐ầ𝑛𝑡ℎ𝑖ế𝑡𝑙ậ𝑝𝑡ℎ𝑢ộ𝑐𝑡í𝑛ℎprimaryKey,vớitrườngkhóachínhnàynếukhôngphảilàdạngdữliệusốnguyênIntegervàtăngtựđộng,chúngtacầnthiếtlậpthuộctínhincrementing thành false.
4.3.3 Log thời gian tương tác bản ghi dữ liệu
Mặc định, Eloquent Model yêu cầu các bảng trong CSDL phải có các trường created_at và updated_at lưu giữ thông tin thời gian tạo bản ghi và thời gian bản ghi này được cập nhật. Tính năng này giúp bạn quản lý các bản ghi trong CSDL rất tốt, đặc biệt trong các bảng liên quan đến giao dịch như đặt hàng, thanh toán… Tuy nhiên, nếu bạn không muốn tính năng này, thiết lập thuộc tính $timestamps thành false.
namespace App;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = false;
}
Như vậy chúng ta có thể thấy là Eloquent Model thiết lập ngầm định một số cấu hình, chúng ta nên tuân thủ theo một tiêu chuẩn lập trình, như vậy sẽ giảm được thời gian viết mã và cũng dễ bảo trì mã nguồn sau này.
5. Sử dụng Eloquent Model tương tác với database
Với mỗi một Model chúng ta sẽ có 4 hành động mà chúng ta quan tâm, nó tương ứng với 4 hành động với các bản ghi dữ liệu (record):
- Create: tạo ra một Model, tương ứng với việc tạo ra một bản ghi dữ liệu trong bảng.
- Read: truy vấn dữ liệu từ database và map lên Model
- Update: chỉnh sửa một Model hay tương ứng là chỉnh sửa một bản ghi.
- Delete: xóa một Model và tương ứng là xóa một bản ghi dữ liệu.
5.1 Insert dữ liệu
5.1.1 Tạo một bảng ghi dữ liệu
Như đã nói ở trên, việc thêm một bản ghi dữ liệu sẽ tương ứng với tạo một thực thể hay một cài đặt (instance) từ Model.
<?php
use App\Product;
$product = new Product;
$product->name = $request->input('name');
$product->price = $request->input('price');
$product->content = $request->input('content');
$product->active = $request->has('active')? 1 : 0;
$product->save();
Tạo một thực thể bằng câu lệnh new Ten_model, ở đây tạo ra một thực thể là $product từ Model Product, sau đó gán giá trị cho các thuộc tính của chúng. Khi muốn lưu record này vào database chúng ta gọi đến phương thức save(). Chú ý, các trường created_at, updated_at sẽ được cập nhật các giá trị thời gian một cách tự động do đó bạn chỉ cần quan tâm đến các trường này khi sử dụng giá trị của chúng. Chúng ta cùng xem lại nếu như không sử dụng Laravel Eloquent chúng ta cũng có thể sử dụng Query Builder để thực hiện, và khi đó chúng ta phải tự chủ động xử lý các công việc mà Laravel Eloquent đã ngầm định như đưa vào giá trị thời gian cho created_at, updated_at chẳng hạn.
// Code trích dẫn trong phương thức store của ProductController trong ví dụ Query Builder
$active = $request->has('active')? 1 : 0;
$product_id = DB::table('products')->insertGetId([
'name' => $request->input('name'),
'price' => $request->input('price'),
'content' => $request->input('content'),
'image_path' => $request->input('image_path'),
'active' => $active,
'created_at' => \Carbon\Carbon::now(),
'updated_at' => \Carbon\Carbon::now()
]);
5.1.2 Model Mass Assignment
Mass Assignment là gì? Mass Assignment xuất phát từ ngôn ngữ Ruby on Rails, là tính năng cho phép lập trình một cách tự động gán các tham số của một HTTP request vào các biến hoặc đối tượng trong lập trình. Ví dụ: chúng ta có một form đăng ký người dùng như sau, các tên trường nhập liệu trùng với tên cột trong bảng users trong CSDL.
<form>
<input name='username' type='text'>
<input name='password' type='text'>
<input name='email' type='text'>
<input type=submit>
</form>
Khi đó form này POST dữ liệu lên chúng ta có thể ghi dữ liệu này vào CSDL bằng đoạn code sau:
$user = new User(Input::all());
Thật ngắn gọn và đơn giản đúng không, tính năng này gọi là Mass Assignment. Tuy nhiên, có một lỗ hổng bảo mật xảy ra, nếu một kẻ xấu gửi thêm dữ liệu user_type = ‘admin’, khi đó user mới được tạo sẽ có quyền admin, việc gắn thêm dữ liệu gửi lên server là rất đơn giản có thể thực hiện bằng các công cụ có sẵn trên trình duyệt như Chrome Developer Tools… Để xử lý vấn đề lỗ hổng trong Mass Assignment, Laravel đưa ra thêm hai thuộc tính cho Model là 𝑓𝑖𝑙𝑙𝑎𝑏𝑙𝑒𝑣àfillablevàguarded. Ví dụ:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = ['name', 'password', 'email'];
}
$fillable cho phép thiết lập các cột trong một bảng có thể sử dụng tính năng Mass Assignment, khi đó ta có thể thực hiện:
$user = User::create(Input::all());
// Hoặc
$user = new User(Input::al());
Khi đó nếu kẻ xấu gửi thêm user_type là trường không có trong fillable, các câu lệnh trên sẽ phát sinh một exception ngay. Như vậy lỗ hỏng trong MassAssignment đã được xử lý. Trái ngược với fillable, ta có thể định nghĩa các trường được bảo vệ khỏi Mass Assignment thông qua thuộc tính $guarded. Chú ý, không khai báo cả hai thuộc tính này đồng thời. Chú ý: một vấn đề nữa là 𝑓𝑖𝑙𝑙𝑎𝑏𝑙𝑒𝑣àfillablevàguarded chỉ có tác dụng với các phương thức của Eloquent Model, với các phương thức của Query Builder nó không có tác dụng.
Ví dụ sau minh chứng cho điều này:
// Phương thức Eloquent
$user = User::find($id);
$user->update(Input::all());
// Phương thức Query Builder
User::where('id', $id)->update(Input::all());
Khi đó nếu kẻ xấu có tình chèn thêm user_type = ‘admin’ thì đoạn code đầu sẽ phát sinh exception còn đoạn code sau vẫn chạy bình thường.
5.1.3 Một số phương thức tạo bản ghi khác
Có hai phương thức tạo bản ghi mới sử dụng Mass Assignment khác là firstOrCreate và firstOrNew. Phương thức firstOrCreate() thử tìm các bản ghi sử dụng cặp cột và giá trị, nếu không tìm thấy, một bản ghi sẽ được tạo ra với các thuộc tính này. firstOrNew thì khác chút là nó không ghi dữ liệu vào CSDL mà trả về một instance của model, chỉ ghi dữ liệu xuống CSDL khi gọi phương thức save().
// Tìm user trong CSDL nếu không có thì insert bản ghi
$user = User::firstOrCreate(Input::all());
// Tìm user trong CSDL nếu không có thì trả về một instance của User và chỉ ghi xuống CSDL khi gọi phương thức save()
$user = User::firstOrCreate(Input::all());
$user->save();
Một phương thức nữa cũng rất hay gặp trong thực tế là updateOrCreate, nó sử dụng để update hoặc tạo mới một entry, ví dụ
$product = Product::updateOrCreate(
['name' => 'Bộ phát WiFi TENDA FH304', 'active' => 1],
['price' => 750000]
);
5.2 Truy vấn dữ liệu bằng Model
Bạn đã tạo ra các record, giờ là lúc đọc các record này và xử lý chúng. Trong database, chúng ta thường viết các câu truy vấn dữ liệu để lấy dữ liệu, ở trên lớp trên chúng ta cũng thực hiện các truy vấn thông qua Model. Để lấy tất cả các record trong table mà Model thể hiện tương ứng sử dụng phương thức all():
<?php
use App\Product;
$products = Product::all();
foreach ($products as $p) {
echo 'Sản phẩm: ' . $p->name . ' có giá ' . number_format($p->price) . 'VNĐ' ;
}
Đoạn mã trên lấy tất cả các sản phẩm trong bảng products và duyệt qua các thực thể đó rồi in ra màn hình thông tin về sản phẩm. Chúng ta có thể thêm các điều kiện vào truy vấn:
<?php
use App\Product;
$products = Product::where('active', '=', 1)
->where('price', '>', '350000')
->orderBy('name')
->take(10)
->get();
foreach ($products as $p) {
echo 'Sản phẩm: ' . $p->name . ' có giá ' . number_format($p->price) . 'VNĐ' ;
}
Các phương thức sử dụng để thêm điều kiện trong truy vấn như where, orderBy, take… bạn có thể xem thêm trong phần Xây dựng truy vấn database với Laravel Query Builder. Kết quả trả về của các phương thức get(), all() là một instance của lớp Illuminate\Database\Eloquent\Collection. Laravel Collection là một trong những phần cực hay về xử lý dữ liệu dạng tập hợp, nó xây dựng sẵn hàng trăm các hàm giúp bạn viết code cực nhanh với những yêu cầu tổng hợp dữ liệu phức tạp. Ví dụ: bạn muốn tính tổng theo các nhóm thỏa mãn một điều kiện trong một tập hợp chẳng hạn, chỉ cần 1 dòng code là bạn có thể thực hiện được. Khó tin nhỉ, xem phần Laravel Collection để kiểm chứng nhé. Bạn nào đã từng làm quen với Lodash một thư viện Javascript tuyệt vời cho xử lý các tập hợp thì Laravel Collection cũng tương tự như vậy. Hơi dài dòng một chút vì có nhiều điều cần viết quá nhưng thôi chúng ta lại tập trung vào chủ đề của bài viết.
Tình huống tiếp theo, chúng ta muốn tìm một sản phẩm khi biết id của sản phẩm hoặc muốn lấy một sản phẩm bất kỳ có giá 300k chẳng hạn, dễ dàng thực hiện với Eloquent Model:
<?php
use App\Product;
$product = Product::find($product_id);
echo 'Sản phẩm: ' . $product->name . ' có ID là ' . $product_id;
$product_300k = Product::where('price', '>', 300000)->first();
echo 'Sản phẩm: ' . $product->name . ' có giá ' . $product->price . ' VNĐ';
Chú ý, phương thức find() có thể truyền vào một mảng các id của sản phẩm
<?php
use App\Product;
// Trả về Collection 3 sản phẩm có ID 1,2,3
$products = Product::find([1,2,3]);
Các truy vấn cũng có thể đưa vào các hàm tổng hợp dữ liệu như với Laravel Query Builder như count(), max(), min()…
<?php
use App\Product;
// Số lượng sản phẩm đang đăng bán (có trạng thái active = 1)
$product_cnt = Product::where('active', '=', 1)->count();
// Giá sản phẩm rẻ nhất đang đăng bán
$min_price = Product::where('active', '=', 1)->min('price');
5.3 Cập nhật dữ liệu
Khi muốn update giá trị cột nào đó trong bảng, cũng đơn giản là tạo instance của Model tương ứng và sử dụng phương thức save() như ở trên:
<?php
use App\Product;
$product_id = 5;
$product = Product::find($product_id);
$product->name = 'New product name';
$product->price = 500000;
$product->save();
Trên đây chúng ta update một bản ghi, vậy update cùng lúc nhiều bản ghi thì làm thế nào trong Eloquent Model? Ví dụ, tất cả các sản phẩm TENDA hiện đang hết hàng và chúng ta muốn chuyển chúng sang chế độ không đăng bán active = 0, chúng ta thực hiện như sau:
Product::where('active', 1)
->where('name', 'like', '%TENDA%')
->update(['active' => 0]);
5.4 Xóa bản ghi dữ liệu
Xóa bản ghi dữ liệu đơn giản bằng cách gọi phương thức delete() trên thực thể của Model:
$product = Product::find(1);
$product->delete();
Hoặc chúng ta có thể truy vấn dữ liệu và xóa dựa trên kết quả truy vấn: Xóa tất cả các sản phẩm đang không active.
$deletedProducts = Product::where('active', 0)->delete();
5.4.1 Xử lý bản ghi đã xóa
Trước đây, khi dung lượng và bộ xử lý trong máy chủ là một cái gì đó xa xỉ, các bản ghi cần được xóa triệt để khỏi cơ sở dữ liệu, hiện nay thì đã khác nhiều, chúng ta không cần quan tâm nhiều đến dung lượng lưu trữ. Các bản ghi cũng vì vậy chỉ được xóa mềm (soft delete), thực chất là thêm một trường đánh dấu là bản ghi này đã xóa. Các dữ liệu đã xóa là rất cần thiết cho việc phân tích hành vi người dùng hoặc kiểm tra debug các ứng dụng. Laravel hỗ trợ xóa mềm một bản ghi bằng cách thêm một thuộc tính deleted_at vào Model cũng như ở table trong database. Để cho phép một Model có thể thực hiện được đánh dấu bản ghi đã xóa, chúng ta sử dụng trait Illuminate\Database\Eloquent\SoftDeletes và thêm deleted_at vào thuộc tính $dates của nó:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Product extends Model
{
use SoftDeletes;
/**
* The attributes that should be mutated to dates.
*
* @var array
*/
protected $dates = ['deleted_at'];
}
Khi đó, nếu bạn thực hiện phương thức delete() thay vì nó sẽ xóa record đó đi thì nó sẽ cập nhật thời gian hiện tại vào trường deleted_at, và như vậy bản ghi này đã được đánh dấu là đã xóa. Chúng ta cũng có thể kiểm tra xem một thực thể của Model là được xóa mềm hay không bằng phương thức trashed()
if ($product->trashed()) {
// Sản phẩm này đã được đánh dấu là đã xóa
}
5.4.2 Truy vấn các bản ghi được xóa “mềm”
Các bản ghi được đánh dấu là đã xóa sẽ không có kết quả trong các truy vấn dữ liệu thông thường, để thêm vào các kết quả từ các bản ghi đã xóa “mềm” chúng ta sử dụng phương thức withTrashed():
$comments = Comment::withTrashed()
->where('user_id', 1)
->get();
Nó sẽ trả về tất cả các bình luận của người dùng có id là 1 với cả các bình luận thông thường và bình luận đã được xóa “mềm”. Ngược lại nếu muốn chỉ truy vấn các kết quả trong các record đã được xóa mềm, sử dụng phương thức onlyTrashed():
$comments = Comment::onlyTrashed()
->where('user_id', 1)
->get();
5.4.3 Khôi phục các bản ghi đã xóa “mềm”
Xóa mềm là một cách rất hay do khi cần thiết chúng ta hoàn toàn có thể khôi phục được dữ liệu đã “xóa”, với phương thức restore(), bản ghi đã được khôi phục hoàn toàn:
// Khôi phục một bản ghi đã xóa "mềm"
$comment->restore();
// Khôi phục nhiều bản ghi đã xóa "mềm" thông qua truy vấn
Comment::onlyTrashed()->where('user_type', '=', 'admin')->restore();
5.4.4 Xóa vĩnh viễn bản ghi
Chúng ta có xóa “mềm” thì cũng phải có xóa “cứng” hay còn gọi là xóa vĩnh viễn bản ghi, tức là khi thực hiện bản ghi đó sẽ không còn trong database và như vậy các truy vấn thường lẫn truy vấn xóa “mềm” không còn kết quả gì liên quan. Thực hiện xóa “cứng” bằng phương thức forceDelete():
// Xóa cứng một bản ghi
$comment->forceDelete();
6. Query Scope – Phạm vi truy vấn
Phạm vi truy vấn xuất phát từ một vấn đề khi chúng ta muốn thực hiện cùng một số điều kiện ràng buộc truy vấn với một hoặc nhiều các truy vấn, chúng ta không cần phải lặp lại chúng cho tất các truy vấn mà chỉ cần định nghĩa các Scope và sử dụng lại chúng trong định nghĩa Model. Tính năng xóa “mềm” (Soft delete) ở phần trên là một ví dụ, các ràng buộc về trường deleted_at được lặp lại cho tất cả các truy vấn, thay vì truy vấn nào chúng ta cũng đưa thêm các ràng buộc với trường deleted_at thì chúng ta tạo ra các Scope.
6.1 Phạm vi toàn cục
Với phạm vi toàn cục, định nghĩa Scope này sẽ được áp dụng cho một Model và tất cả các truy vấn liên quan đến Model đó sẽ được áp dụng thêm ràng buộc. Ví dụ chúng ta tạo ra một Scope có tên là AgeScope.php nằm trong thư mục app\scopes (nếu chưa có bạn tự tạo ra):
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class AgeScope implements Scope
{
/**
* Apply the scope to a given Eloquent query builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
public function apply(Builder $builder, Model $model)
{
$builder->where('age', '>', 18);
}
}
Đây là một lớp kế thừa lại Illuminate\Database\Eloquent\Scope và sử dụng phương thức apply() để khai báo các ràng buộc thêm vào. Ở đây, chúng ta thêm vào một ràng buộc là tuổi phải lớn hơn 18. Khi muốn áp dụng Scope với phạm vi toàn cục này, thực hiện ghi đè phương thức boot với việc khai báo thêm phương thức addGlobalScope().
<?php
namespace App;
use App\Scopes\AgeScope;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The "booting" method of the model.
*
* @return void
*/
protected static function boot()
{
parent::boot();
static::addGlobalScope(new AgeScope);
}
}
Khi đó, mọi truy vấn bằng Model User sẽ được ràng buộc thêm là người dùng phải đủ 18 tuổi, khi đó nếu bạn sử dụng User::all() thì nó sẽ build ra truy vấn có dạng:
select * from `users` where `age` > 18
Laravel cũng cho phép sử dụng Closure, là một cách định nghĩa hàm nâng cao định nghĩa hàm trong một hàm:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class User extends Model
{
/**
* The "booting" method of the model.
*
* @return void
*/
protected static function boot()
{
parent::boot();
static::addGlobalScope('age', function (Builder $builder) {
$builder->where('age', '>', 18);
});
}
}
Nếu muốn truy vấn mà không bị ảnh hưởng bởi các Scope đã được áp dụng vào Model sử dụng phương thức withoutGlobalScopes(), phương thức này nếu không truyền tham số nào vào nó sẽ bỏ qua tất cả các Scope được áp dụng hoặc chúng ta có thể chỉ muốn bỏ các Scope nào đó thì truyền vào mảng tên các Scope đó.
// Bỏ đi toàn bộ các Scope được áp dụng trong Model User
User::withoutGlobalScopes()->get();
// Chỉ bỏ các Scope là FirstScope và SecondScope
User::withoutGlobalScopes([
FirstScope::class, SecondScope::class
])->get();
6.2 Phạm vi cục bộ
Các Scope phạm vi cục bộ cho phép bạn định nghĩa các tập ràng buộc để dễ dàng sử dụng cho chính Model đó, do vậy các Scope phạm vi cục bộ được định nghĩa ngay trong Model.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Phạm vi truy vấn là các User đã bình chọn lớn hơn 100
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopePopular($query)
{
return $query->where('votes', '>', 100);
}
/**
* Phạm vi truy vấn là các User đang hoạt động
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeActive($query)
{
return $query->where('active', 1);
}
}
Chú ý các Scope phạm vi cục bộ được định nghĩa bởi các hàm trong Model với tên bắt đầu bằng scope (scopePopular, scopeActive…). Khi đó bạn có thể sử dụng các Scope đã được định nghĩa này trong các truy vấn như sau:
$users = User::popular()->active()->orderBy('created_at')->get();
Lấy tất cả các user có lượng bình chọn lớn hơn 100 và đang hoạt động.
6.3 Phạm vi truy vấn động
Đôi khi bạn muốn các phạm vi truy vấn chấp nhận một tham số đầu vào, cùng xem ví dụ sau:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Scope a query to only include users of a given type.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param mixed $type
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeOfType($query, $type)
{
return $query->where('type', $type);
}
}
Sau đó, chúng ta có thể truyền tham số khi gọi các Scope này:
$users = App\User::ofType('admin')->get();
7. Các sự kiện khi thao tác với cơ sở dữ liệu
Eloquent Model sẽ tạo ra các sự kiện khi thao tác với cơ sở dữ liệu, các sự kiện này bao gồm: creating, created, updating, updated, saving, saved, deleting, deleted, restoring, restored. Với các sự kiện này, bạn dễ dàng thực hiện được các công việc khác trước khi thực hiện một thao tác nào đó với database. Để khai báo sử dụng các sự kiện này với một Model, chúng ta sử dụng thuộc tính $events:
<?php
namespace App;
use App\Events\UserSaved;
use App\Events\UserDeleted;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
/**
* The event map for the model.
*
* @var array
*/
protected $events = [
'saved' => UserSaved::class,
'deleted' => UserDeleted::class,
];
}
Khi đó Model User sẽ tạo ra các event saved và deleted khi các bản ghi được lưu và các bản ghi được xóa khỏi cơ sở dữ liệu. Công việc còn lại là lắng nghe các sự kiện và xử lý chúng khi sự kiện xảy ra, chúng ta sẽ bàn vấn đề này kỹ hơn trong phần Laravel Event, quản lý sự kiện trong ứng dụng.
8. Lời kết
Bạn thấy làm việc với cơ sở dữ liệu thông qua Laravel Eloquent Model thật thú vị phải không? Đúng với phương châm của Taylor Otwell tác giả của Laravel: “Lập trình phải là một trải nghiệm tuyệt vời”, Laravel luôn đưa những công việc làm thông thường thành các công cụ giúp giảm thời gian viết code và chỉ tập trung chính vào logic nghiệp vụ. Chúng ta cũng đã hoàn thành tìm hiểu các Model trong Laravel, trong phần 2 tới chúng ta sẽ tìm hiểu về Cách xử lý mối quan hệ trong database thông qua Eloquent Model.